package pl.wroc.pwr.nn;

import java.util.ArrayList;
import java.util.Collections;

public class NeuralNetwork {

	private int numberOfHiddens;
	private int numberOfOutputs;
	private int numberOfInputs;

	double[] in;
	double[] out;

	Node[] hiddenLayer;
	Node[] outputLayer;

	float theta;

	public NeuralNetwork(int inputs, int outputs, int hidden, float theta) {

		numberOfInputs = inputs;
		numberOfHiddens = hidden;
		numberOfOutputs = outputs;
		this.theta = theta;

		in = new double[numberOfInputs];
		out = new double[numberOfOutputs];

		// tworzenie jednostek ukrytych
		hiddenLayer = new Node[numberOfHiddens];
		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenLayer[i] = new Node(numberOfInputs, theta);
		}

		// tworzenie jednostek wyjsciowych
		outputLayer = new Node[numberOfOutputs];
		for (int i = 0; i < numberOfOutputs; i++) {
			outputLayer[i] = new Node(numberOfHiddens, theta);
		}
	}

	public void runBackPropagationLearing(InputExample ex) {

		// laczenie wejsc jednostek ukrytych z wejsciami sieci
		for (Node hiddenNode : hiddenLayer) {
			double[] input = ex.getInput();
			hiddenNode.input = input;
		}

		// liczenie wyjscia
		double[] hiddenOutputs = new double[numberOfHiddens];
		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenOutputs[i] = hiddenLayer[i].output;
		}

		// laczenie
		for (Node outputNode : outputLayer) {
			outputNode.input = hiddenOutputs;
		}

		for (int i = 0; i < numberOfHiddens; i++) {
			// liczenie wartosci net
			hiddenLayer[i].calcNet();
			// liczenie wartosci funkcji aktywacji(wyjscia)
			hiddenLayer[i].output();
		}

		// uaktualnianie wejscia wartwy wyjsciowej

		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenOutputs[i] = hiddenLayer[i].output;
		}

		// laczenie
		for (int i = 0; i < numberOfOutputs; i++) {
			outputLayer[i].input = hiddenOutputs;

		}

		for (int i = 0; i < numberOfOutputs; i++) {
			// liczenie wartosci net
			outputLayer[i].calcNet();
			// liczenie wartosci funkcji aktywacji(wyjscia)
			outputLayer[i].output();
		}

		// liczenie bledu jednostek wyjsciowych
		for (int i = 0; i < numberOfOutputs; i++) {

			outputLayer[i].calcuErrorOutputLayer(ex.getOutput()[i]);

		}

		// liczenie bledu jednostek ukrytych
		for (int i = 0; i < numberOfHiddens; i++) {

			// tworzenie tablicy wag
			double we[] = new double[numberOfOutputs];
			for (int j = 0; j < numberOfOutputs; j++) {
				we[j] = outputLayer[j].weight[i];

			}

			// tworzenie talicy bledow

			double err[] = new double[numberOfOutputs];
			for (int j = 0; j < numberOfOutputs; j++) {
				err[j] = outputLayer[j].error;
				// System.out.println("err:"+err[j]);
			}

			hiddenLayer[i].calcuErrorHiddenLayer(we, err);

		}

		// uaktualnianie wag
		for (int i = 0; i < numberOfOutputs; i++) {
			hiddenLayer[i].setNewWeight();
			outputLayer[i].setNewWeight();
		}

	}

	public int check(InputExample example) {

		// laczenie wejsc jednostek ukrytych z wejsciami sieci

		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenLayer[i].input = example.getInput();
		}

		// laczenie wyjscie warstwy ukrytej z wejsciami warstwy wychodzacej
		// tworzenie tablicy wyjsc wartwy ukrytej
		double[] hiddenOutput = new double[numberOfHiddens];
		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenOutput[i] = hiddenLayer[i].output;
		}
		
		//System.out.println("Pierwszy");
		for(Node n : outputLayer) {
		//	System.out.print(String.valueOf(n.output()).substring(0, 5)+"  ");
		}
		//System.out.println("");

		// laczenie
		for (int i = 0; i < numberOfOutputs; i++) {
			outputLayer[i].input = hiddenOutput;

		}

		for (int i = 0; i < numberOfHiddens; i++) {
			// liczenie wartosci net
			hiddenLayer[i].calcNet();
			// liczenie wartosci funkcji aktywacji(wyjscia)
			hiddenLayer[i].output();
		}

		// uaktualnianie wejscia wartwy wyjsciowej

		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenOutput[i] = hiddenLayer[i].output;
		}

		// laczenie
		for (int i = 0; i < numberOfOutputs; i++) {
			outputLayer[i].input = hiddenOutput;

		}

		for (int i = 0; i < numberOfHiddens; i++) {
			// liczenie wartosci net
			hiddenLayer[i].calcNet();
			// liczenie wartosci funkcji aktywacji(wyjscia)
			hiddenLayer[i].output();
		}

		// uaktualnianie wejscia wartwy wyjsciowej

		for (int i = 0; i < numberOfHiddens; i++) {
			hiddenOutput[i] = hiddenLayer[i].output;
		}

		// laczenie
		for (int i = 0; i < numberOfOutputs; i++) {
			outputLayer[i].input = hiddenOutput;

		}

		for (int i = 0; i < numberOfOutputs; i++) {
			// liczenie wartosci net
			outputLayer[i].calcNet();
			// liczenie wartosci funkcji aktywacji(wyjscia)
			outputLayer[i].output();
		}

		printStats();

		ArrayList<Double> outputs = new ArrayList<Double>();
		for (int i = 0; i < numberOfOutputs; i++) {
			outputs.add(outputLayer[i].output);
		}

		//System.out.println("max elem is "+(Collections.max(outputs)));
		return outputs.indexOf(Collections.max(outputs));
	}

	private void printStats() {
		StringBuilder sb = new StringBuilder("Prawdopodobienstwo rozpoznania: ");
		boolean wrong = false;
		for (int i = 0; i < numberOfOutputs; i++) {
			if(outputLayer[i].output > 1.0d) {
				wrong = true;
			}
			sb.append(String.format("%1.3f", outputLayer[i].output)
					+ " ");
		}
		System.out.println(sb.toString());
	}
}
